#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dfs.h"
#include "cache.h"
#include "sglib.h"


FORWARD _PROTOTYPE( int get_cache_stats, (CacheStats *) 								 );

/* sglib stuff */
#ifndef HASHFUNCTION

#define HASHFUNCTION
unsigned int CacheElement_hash_function(CacheElement *el) {
	CacheElement *temp;
	unsigned int h = 0,i;
	
	temp = malloc(sizeof(CacheElement));
	memcpy(temp,el,sizeof(CacheElement));
	
	for(i=1;i<strlen(temp->key);i++)
		for(; temp->key[i]; temp->key[i]++)
			h = MULT * h + temp->key[i];
			
	free(temp);
	return h % NHASH;
}
#endif
 
SGLIB_DEFINE_DL_LIST_PROTOTYPES(CacheElement, CACHEKEY_COMPARATOR, previous, next)
SGLIB_DEFINE_DL_LIST_FUNCTIONS(CacheElement, CACHEKEY_COMPARATOR, previous, next)
SGLIB_DEFINE_HASHED_CONTAINER_PROTOTYPES(CacheElement, HASH_TAB_SIZE, CacheElement_hash_function)
SGLIB_DEFINE_HASHED_CONTAINER_FUNCTIONS(CacheElement, HASH_TAB_SIZE, CacheElement_hash_function)

/* Cache creator */
PUBLIC void init_cache(void)
{
	sglib_hashed_CacheElement_init(CacheTable);
}

/* Cache destroyer */
PUBLIC void destroy_cache(void)
{
	struct sglib_hashed_CacheElement_iterator it;
	CacheElement *le;
	
	for(le=sglib_hashed_CacheElement_it_init(&it,CacheTable); le!=NULL; le=sglib_hashed_CacheElement_it_next(&it)) {
		free(le);
	}
}
/* Cache creator */
PUBLIC void insert_to_cache(const char *key,char *deleted_key){
	char dummy[PATH_MAX];
	struct sglib_hashed_CacheElement_iterator it;
	CacheElement *ne, *le;
	CacheStats cache_st;
	
	/* Preparing cache entry */
	ne = malloc(sizeof(CacheElement));
	memset(ne->key,0,NAME_MAX);
	memcpy(ne->key,key,strlen(key));
	/*sprintf(ne->key,key,strlen(key))*/
	sprintf(ne->value,"%s/%s.rep",REPLICAS_FOLDER,ne->key);
	ne->hits = -1; /* so the first add sets it to 0 */
	ne->miss_st = 0;
	
	printf("%s vs %s\n",ne->key,key);
	/* no deleted key so far */
	deleted_key = NULL;
	
	/* Increase most recent hit status to assume *
	 * least recently used element				 */
	get_cache_stats(&cache_st);
	if(cache_st.cache_sz>=HASH_TAB_SIZE){
		for(le = sglib_hashed_CacheElement_it_init(&it,CacheTable); le!=NULL; le = sglib_hashed_CacheElement_it_next(&it)){
			if(le->miss_st >= HASH_TAB_SIZE - 1){
				/* Delete eldest entry */
				sglib_hashed_CacheElement_delete(CacheTable, le);
				printf("Deleting replica for key :%s\n",le->key);
				deleted_key = le->key;
				printf(" Deleted replica : %s",le->key);
				free(le);
				break;
			}
		}
	}
	/* check if element belongs to cache */
	if(belongs_to_cache(ne->key,dummy) == ERROR){ 
		sglib_hashed_CacheElement_add(CacheTable, ne);
		printf("Just Added:%s\n",ne->key);
	}else{
		printf("Was in cache already : %s\n",ne->key);
	}
	
	/* updating cache stats */
	update_cache_stats(ne->key);
	for(le = sglib_hashed_CacheElement_it_init(&it,CacheTable); le!=NULL; le = sglib_hashed_CacheElement_it_next(&it)){
		printf("%s %s : %d %d \n",le->key,le->value,le->hits,le->miss_st);
	}
	
}

/* Updating cache data with a given key */
PUBLIC void update_cache_stats(const char *key){

	struct sglib_hashed_CacheElement_iterator it;
	CacheElement *le;
	/* Update stats */
	for(le = sglib_hashed_CacheElement_it_init(&it,CacheTable); le!=NULL; le = sglib_hashed_CacheElement_it_next(&it))
		if(strcmp(le->key,key)) (le->miss_st)++;
		else{ 
			le->miss_st=0; 
			le->hits++;
		}
}

/* checks if key belongs to cache */
PUBLIC int belongs_to_cache(const char *key,char *res){

	int r;
	CacheElement ce,*ne;
	
	memset(ce.key,0,NAME_MAX);
	memcpy(ce.key,key,strlen(key));
	printf("ce@belongs to  : %s\n",ce.key);
	
	if((ne = sglib_hashed_CacheElement_find_member(CacheTable, &ce))!=NULL){
		printf("found : %s %s",ne->key,ne->value);
		memcpy(res,ne->value,strlen(ne->value));
		return OK;
	}
	printf("its %s\n",(ne==NULL)?"NULL":"not NULL");
	return ERROR;
}

/* get cache statistics */
PRIVATE int get_cache_stats(CacheStats *cache_st){
	
	int i=0;
	
	CacheElement *le;
	struct sglib_hashed_CacheElement_iterator ListIt;
	
	/* TODO: Within below loops fill in cache_st with detailed	*
	 * info about what cache consists of in a final state		*/
	for(le = sglib_hashed_CacheElement_it_init(&ListIt,CacheTable); le!=NULL && i<10; le = sglib_hashed_CacheElement_it_next(&ListIt),i++)
		printf("%s [MISS_ST]:%d\n",le->key,le->miss_st);

	/* Get total number of entries in cache */
	cache_st->cache_sz = 0;
	for(i=0;i<HASH_TAB_SIZE;i++){
		cache_st->cache_sz += sglib_CacheElement_len(CacheTable[i]);
	}
}
/*
PRIVATE char * construct_cmd(const char *key){
	
	char *diff_cmd, *rep_name;

	rep_name = malloc(100*sizeof(char ));
	diff_cmd = malloc(CMD_SIZE*sizeof(char));
	
	strncpy(rep_name,(char*)key,sizeof(key));
	strcat((char*) rep_name,REPLICA_EXT);
	
	strncpy(diff_cmd,UNIFIED_DIFF_CMD, sizeof(UNIFIED_DIFF_CMD));
	

	strcat(diff_cmd,key);
	strcat(diff_cmd,SPACE);
	strcat(diff_cmd,rep_name);
	strcat(diff_cmd,SPACE);
	strcat(diff_cmd,APPEND);
	strcat(diff_cmd,SPACE);
	strcat(diff_cmd,DIFF_FILE);
	
	return diff_cmd;
}

PRIVATE char *construct_patch_cmd(void){
	char *patch_cmd;
	patch_cmd = malloc(CMD_SIZE*sizeof(char));
	strncpy(patch_cmd,PATCH_CMD, sizeof(PATCH_CMD));
	strcat(patch_cmd,SPACE);
	strcat(patch_cmd,"<");
	strcat(patch_cmd,SPACE);
	strcat(patch_cmd,DIFF_FILE);
	return patch_cmd;
}

PRIVATE void delete_replica(const char *key){
	char str[PATH_MAX];
	char rep_path[PATH_MAX];
	
	sprintf(rep_path,"%s/%s",REPLICAS_FOLDER,key);
	remove(rep_path);
}

PRIVATE void apply_patch(const char *diff_cmd, const char *patch_cmd){
	system(diff_cmd);
	system(patch_cmd);
}*/

